package com.demo.webapp.servlet;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

/**
 * 文件下载Servlet
 * @author ryuu.kk
 *
 */
public abstract class DownloadServlet extends HttpServlet {

	/**
	 * 版本序列化ID
	 */
	private static final long serialVersionUID = -663631108493095027L;
	
	/**
	 * 默认文件放置地址
	 */
	private static final String DESTINATION_DIR_PATH ="/files/";
	
	/**
	 * 下载文件时一次读取的字节数
	 */
	private static final int BUFFER_SIZE = 1024;
	
	/**
	 * 临时文件位置 
	 * 该位置对文件进行缓存
	 */
	private String tmpPath = DESTINATION_DIR_PATH;
	
	/**
	 * Servlet初始化
	 * 初始化文件缓存路径和临时文件目录
	 */
	public void init(ServletConfig config) throws ServletException {
		
		super.init(config);
		
		String pTmpPath = getServletContext().getInitParameter("tmpPath");
		
		if (StringUtils.isNotEmpty(pTmpPath)) {
			this.tmpPath = pTmpPath;
		}
		// 取得webapp相对路径
		tmpPath = config.getServletContext().getRealPath(tmpPath);
		File file = new File(tmpPath);
		if (!file.exists()) {
			file.mkdirs();
		}
		if(!file.isDirectory()) {
			throw new ServletException(tmpPath + " is not a directory");
		}
	}

	/**
	 * 处理文件的下载请求
	 * @param request HttpServletRequest
	 * @param response HttpServletResponse
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)	throws ServletException, java.io.IOException {
		
		//取得文件信息
		FileInfo fileInfo = null;
		try {
			fileInfo = getFileInfo(request);
		} catch (FileNotFoundException e) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
			return;
		}
		
		//下载权限判断
		if (!hasPermission(fileInfo.getFileId(), request)) {
			//throw new AccessDeniedException("");
			response.sendError(HttpServletResponse.SC_FORBIDDEN, "not permission error!");
			return;
		}
		
		//请求的前置处理方法
		if (!preDownload(request, response, fileInfo)) {
			//throw new AccessDeniedException("");
			response.sendError(HttpServletResponse.SC_NOT_FOUND, "file not found");
			return;
		}
		
		InputStream inputStream = null;
		ServletOutputStream outputStream = null;
		
		try {
			inputStream = getFileInputStream(fileInfo);
			outputStream = response.getOutputStream();
			
			int count;
			byte[] buffer = new byte[BUFFER_SIZE];
	        while ((count = inputStream.read(buffer)) > 0) {
	        	outputStream.write(buffer, 0, count);
	        }

		} catch(ServletException e) {
			//以后对错误消息进行友好的处理...- -
			response.setContentType("text/html; charset=UTF-8");
			response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
		} catch (IOException e) {
			response.setContentType("text/html; charset=UTF-8");
			response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {}
			}
			if (outputStream != null) {
				try {
					outputStream.flush();
					outputStream.close();
				} catch (IOException e) {}
			}
			//请求的后置处理方法
			postDownload(fileInfo);
		}
	}
	
	/**
	 * 取得文件
	 * @param request HttpServletRequest
	 * @param response HttpServletResponse
	 * @see #doGet
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException {
		doGet(request, response);
	}
	
	/**
	 * 前置下载处理
	 * 对HTTP的头信息进行处理
	 * @param request HttpServletRequest
	 * @param response HttpServletResponse
	 * @throws UnsupportedEncodingException 不支持指定的编码
	 */
	protected boolean preDownload(HttpServletRequest request, HttpServletResponse response, FileInfo fileInfo) throws UnsupportedEncodingException {
		
		//取得文件名
		String fileName = fileInfo.getFileName();
		//文件类型
		String contentType = fileInfo.getContentType();
		
		//文件名和文件类型不能为空
		if (StringUtils.isBlank(fileName) || StringUtils.isBlank(contentType)) {
			return false;
		}
		
    	String agent = (String) request.getHeader("USER-AGENT");  
    	if (agent != null && agent.indexOf("MSIE") > 0) {
    		fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
    	} else {
    		fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
    	}
    	response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
    	
		//设置响应头
		response.setCharacterEncoding("UTF-8");
    	response.setHeader("Content-Language", "UTF-8");
    	response.setContentType(contentType);
    	response.setHeader("Pragma", "no-cache");
    	response.setHeader("cache-control", "no-cache");
    	response.setHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
    	
    	return true;
	};
	
	/**
	 * 文件下载请求的后置处理函数
	 * @param fileId 文件ID
	 * @return 操作标志位
	 */
	protected boolean postDownload(FileInfo fileInfo) {
		//do something
		return true;
	};

	/**
	 * 取得文件输入流方法
	 * @param fileId 文件ID
	 * @return InputStream
	 * @throws ServletException Servlet异常
	 */
	protected abstract InputStream getFileInputStream(FileInfo fileInfo) throws ServletException ;
	
	/**
	 * 取得文件ID
	 * @param params 参数Map
	 * @param defaultId 默认ID
	 * @return 返回文件ID
	 */
	protected abstract FileInfo getFileInfo(HttpServletRequest request) throws FileNotFoundException ;
	
	/**
	 * 对下载是否有许可
	 * @param fileId 文件ID
	 * @param request 请求
	 * @return boolean
	 */
	protected boolean hasPermission(Long fileId, HttpServletRequest request) {
		
		return true;
	}

	public String getTmpPath() {
		return tmpPath;
	}

	/**
	 * 内部类 
	 * 对文件信息进行封装
	 * @author ryuu.kk
	 *
	 */
	protected class FileInfo {
		
		public FileInfo(Long fileId, String uuid, String fileName, String path, String contentType, int size) {
			this.fileId = fileId;
			this.uuid = uuid;
			this.fileName = fileName;
			this.path = path;
			this.contentType = contentType;
			this.size = size;
		}
		
		private final Long fileId;
		private final String uuid;
		private final String fileName;
		private final String path;
		private final String contentType;
		private final int size;
		public Long getFileId() {
			return fileId;
		}
		public String getFileName() {
			return fileName;
		}
		public String getPath() {
			return path;
		}
		public String getContentType() {
			return contentType;
		}
		public int getSize() {
			return size;
		}
		public String getUuid() {
			return uuid;
		}
	}
}
